//
//  NEDDBUserDefaults.m
//  NEDKit
//
//  Created by 000 on 2020/12/7.
//  Copyright © 2020 000. All rights reserved.
//


#import "NEDDBUserDefaults.h"
#import <objc/runtime.h>
#import <LKDBHelper/LKDBHelper.h>
#import <YYModel/NSObject+YYModel.h>

@interface _NEDDBUserDefaultsBean : NSObject
@property (copy, nonatomic) NSString * key;
@property (retain, nonatomic) id value;
@end
@implementation _NEDDBUserDefaultsBean

@end

@interface _NEDDBUserDefaults : NSObject
@property (copy, nonatomic) NSString * key;
/// json数据对应 _NEDDBUserDefaultsBean
@property (copy, nonatomic) NSString * json;
@end
@implementation _NEDDBUserDefaults

@end


@interface NEDDBUserDefaults ()

@property (strong, nonatomic) NSMutableDictionary * mapping;
@property (strong, nonatomic) LKDBHelper * dbHelper;
@property (retain, nonatomic) NSMutableArray <_NEDDBUserDefaultsBean * > * beanList;

@end

@implementation NEDDBUserDefaults


enum NEDTypeEncodings {
    Char                = 'c',
    Bool                = 'B',
    Short               = 's',
    Int                 = 'i',
    Long                = 'l',
    LongLong            = 'q',
    UnsignedChar        = 'C',
    UnsignedShort       = 'S',
    UnsignedInt         = 'I',
    UnsignedLong        = 'L',
    UnsignedLongLong    = 'Q',
    Float               = 'f',
    Double              = 'd',
    Object              = '@'
};

- (LKDBHelper *)dbHelper {
    if (!_dbHelper) {
        _dbHelper = [self.class getUsingLKDBHelper];
    }
    return _dbHelper;
}



- (NSString *)defaultsKeyForSelector:(SEL)selector {
    return [self.mapping objectForKey:NSStringFromSelector(selector)];
}


- (_NEDDBUserDefaultsBean *)dbBeanForKey:(NSString *)key{
    for(_NEDDBUserDefaultsBean * bean in self.beanList){
        if([bean.key isEqualToString:key]){
            return bean;
        }
    }
    NSMutableString * sqlStr = [[NSMutableString alloc] init];
    [sqlStr appendFormat:@"SELECT * FROM @t WHERE key = '%@'",key];
    NSArray * dataList = [self.dbHelper search:_NEDDBUserDefaults.class withSQL:sqlStr];
    _NEDDBUserDefaults * bean = dataList.firstObject;
    if(bean){
        _NEDDBUserDefaultsBean * item = [_NEDDBUserDefaultsBean yy_modelWithJSON:bean.json];
        [self.beanList addObject:item];
        return item;
    }
    return nil;
}

- (id)dbValueForKey:(NSString *)key{
    _NEDDBUserDefaultsBean * bean = [self dbBeanForKey:key];
    return bean.value;
}

- (void)dbSetObject:(id)object forKey:(NSString *)key{
    _NEDDBUserDefaultsBean * bean = [_NEDDBUserDefaultsBean new];
    bean.key = key;
    bean.value = object;
    [self dbSaveValue:bean];
}

- (void)dbSaveValue:(_NEDDBUserDefaultsBean *)bean{
    _NEDDBUserDefaultsBean * oldBean = [self dbBeanForKey:bean.key];
    
    _NEDDBUserDefaults * dbBean = [_NEDDBUserDefaults new];
    dbBean.key = bean.key;
    dbBean.json = [bean yy_modelToJSONString];
    
    if(oldBean){
        NSDictionary * where = @{@"key":bean.key};
        [self.dbHelper updateToDB:dbBean where:where];
        oldBean.value = [bean.value yy_modelCopy];
    }else{
        [self.dbHelper insertToDB:dbBean];
        [self.beanList addObject:bean];
    }
}

- (void)dbDeleteKey:(NSString *)key{
    [self.dbHelper deleteWithClass:_NEDDBUserDefaults.class where:@{@"key":key}];
    [self.beanList enumerateObjectsUsingBlock:^(_NEDDBUserDefaultsBean * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if([obj.key isEqualToString:key]){
            [self.beanList removeObject:obj];
            *stop = YES;
        }
    }];
}


static long long longLongGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self dbValueForKey:key] longLongValue];
}

static void longLongSetter(NEDDBUserDefaults *self, SEL _cmd, long long value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    NSNumber *object = [NSNumber numberWithLongLong:value];
    [self dbSetObject:object forKey:key];
}

static bool boolGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self dbValueForKey:key] boolValue];
}

static void boolSetter(NEDDBUserDefaults *self, SEL _cmd, bool value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self dbSetObject:@(value) forKey:key];
}

static int integerGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self dbValueForKey:key] intValue];
}

static void integerSetter(NEDDBUserDefaults *self, SEL _cmd, int value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self dbSetObject:@(value) forKey:key];
}

static float floatGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self dbValueForKey:key] floatValue];
}

static void floatSetter(NEDDBUserDefaults *self, SEL _cmd, float value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self dbSetObject:@(value) forKey:key];
}

static double doubleGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    return [[self dbValueForKey:key] doubleValue];
}

static void doubleSetter(NEDDBUserDefaults *self, SEL _cmd, double value) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    [self dbSetObject:@(value) forKey:key];
}

static id objectGetter(NEDDBUserDefaults *self, SEL _cmd) {
    NSString * key = [self defaultsKeyForSelector:_cmd];
    __block id value = [self dbValueForKey:key];
    
    NSDictionary * genericMapper = [self.class db_modelContainerPropertyGenericClass];

    [genericMapper enumerateKeysAndObjectsUsingBlock:^(id subkey, Class obj, BOOL *stop) {
        if (![subkey isKindOfClass:[NSString class]] ||
            ![subkey isEqualToString:key]) {
            return;
        }
        if([value isKindOfClass:[NSArray class]] &&
           ![((NSArray *)value).firstObject isKindOfClass:obj]){
            value = [NSArray yy_modelArrayWithClass:obj json:value];
        }else if([value isKindOfClass:[NSSet class]] &&
                 ![((NSSet *)value).anyObject isKindOfClass:obj]){
            NSArray * arr = [NSArray yy_modelArrayWithClass:obj json:value];
            value = [NSSet setWithArray:arr];
        }else if([value isKindOfClass:[NSDictionary class]]){
            value = [obj yy_modelWithJSON:value];
        }
    }];

    return value;
}

static void objectSetter(NEDDBUserDefaults *self, SEL _cmd, id object) {
    NSString *key = [self defaultsKeyForSelector:_cmd];
    if (object) {
        [self dbSetObject:object forKey:key];
    } else {
        [self dbDeleteKey:key];
    }
}

+ (nullable NSDictionary<NSString *, id> *)db_modelContainerPropertyGenericClass{
    return @{};
}

#pragma mark - Begin

+ (instancetype)sharedInstance {
    id sharedInstance = objc_getAssociatedObject(self.class, "sharedInstance");
    if (!sharedInstance) {
        sharedInstance = [[super allocWithZone:NULL] init];
        objc_setAssociatedObject(self.class, "sharedInstance", sharedInstance, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return sharedInstance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self sharedInstance];
}

- (id)copyWithZone:(nullable NSZone *)zone {
    return [[self class] sharedInstance];
}


#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wundeclared-selector"
#pragma GCC diagnostic ignored "-Warc-performSelector-leaks"

- (instancetype)init {
    self = [super init];
    if (self) {
        [self generateAccessorMethods];
        self.beanList = [NSMutableArray new];
    }

    return self;
}

#pragma GCC diagnostic pop

- (void)generateAccessorMethods {
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);

    self.mapping = [NSMutableDictionary dictionary];

    for (int i = 0; i < count; ++i) {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        const char *attributes = property_getAttributes(property);

        char *getter = strstr(attributes, ",G");
        if (getter) {
            getter = strdup(getter + 2);
            getter = strsep(&getter, ",");
        } else {
            getter = strdup(name);
        }
        SEL getterSel = sel_registerName(getter);
        free(getter);

        char *setter = strstr(attributes, ",S");
        if (setter) {
            setter = strdup(setter + 2);
            setter = strsep(&setter, ",");
        } else {
            asprintf(&setter, "set%c%s:", toupper(name[0]), name + 1);
        }
        SEL setterSel = sel_registerName(setter);
        free(setter);

        NSString * key = [NSString stringWithFormat:@"%s", name];
        [self.mapping setValue:key forKey:NSStringFromSelector(getterSel)];
        [self.mapping setValue:key forKey:NSStringFromSelector(setterSel)];

        IMP getterImp = NULL;
        IMP setterImp = NULL;
        char type = attributes[1];
        switch (type) {
            case Short:
            case Long:
            case LongLong:
            case UnsignedChar:
            case UnsignedShort:
            case UnsignedInt:
            case UnsignedLong:
            case UnsignedLongLong:
                getterImp = (IMP)longLongGetter;
                setterImp = (IMP)longLongSetter;
                break;

            case Bool:
            case Char:
                getterImp = (IMP)boolGetter;
                setterImp = (IMP)boolSetter;
                break;

            case Int:
                getterImp = (IMP)integerGetter;
                setterImp = (IMP)integerSetter;
                break;

            case Float:
                getterImp = (IMP)floatGetter;
                setterImp = (IMP)floatSetter;
                break;

            case Double:
                getterImp = (IMP)doubleGetter;
                setterImp = (IMP)doubleSetter;
                break;

            case Object:
                getterImp = (IMP)objectGetter;
                setterImp = (IMP)objectSetter;
                break;

            default:
                free(properties);
                [NSException raise:NSInternalInconsistencyException format:@"Unsupported type of property \"%s\" in class %@", name, self];
                break;
        }

        char types[5];

        snprintf(types, 4, "%c@:", type);
        class_addMethod([self class], getterSel, getterImp, types);
        
        snprintf(types, 5, "v@:%c", type);
        class_addMethod([self class], setterSel, setterImp, types);
    }

    free(properties);
}


@end
